package variabilitysmelldetection.measurement.measures.method;

import java.util.List;

import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.WhileStatement;

import variabilitysmelldetection.measurement.measures.Measure;
import variabilitysmelldetection.measurement.models.ComponentItem;
import variabilitysmelldetection.measurement.models.MethodItem;


public class CyclomaticComplexity extends Measure{

	public CyclomaticComplexity() {
		super("Cyclomatic Complexity", "Cyclo");
	}

	@Override
	public void measure(List<ComponentItem> items) {
		for (ComponentItem classe : items) {
			measure(classe);
		}
	}	

	private void measure(ComponentItem componentItem) {
		double value = 0d;

		List<MethodItem> methods = componentItem.getMethods();
		for (MethodItem m : methods) {
			McCabeVisitor mcb = new McCabeVisitor(m.getSource());
			m.getCompilationUnit(ASTParser.K_STATEMENTS).accept(mcb);
			value = mcb.cyclomatic;

			CyclomaticComplexity cyclo = new CyclomaticComplexity();
			cyclo.setValue(value);
			m.addMeasure(cyclo);
		}
	}

	private class McCabeVisitor extends ASTVisitor {

		private int cyclomatic = 0;
		private String source;

		McCabeVisitor(String source) {
			this.source = source;
			if (!source.equals("")) {
				cyclomatic++;
			}
		}

		public boolean visit(CatchClause node) {
			cyclomatic++;
			return true;
		}
		public boolean visit(ConditionalExpression node) {
			cyclomatic++;
			inspectExpression(node.getExpression());
			return true;
		}
		public boolean visit(DoStatement node) {
			cyclomatic++;
			inspectExpression(node.getExpression());
			return true;
		}
		public boolean visit(ForStatement node) {
			cyclomatic++;
			inspectExpression(node.getExpression());
			return true;
		}
		public boolean visit(IfStatement node) {
			cyclomatic++;
			inspectExpression(node.getExpression());
			return true;
		}
		public boolean visit(SwitchCase node) {
			if (!node.isDefault()) cyclomatic++;			
			return true;
		}
		public boolean visit(WhileStatement node) {
			cyclomatic++;
			inspectExpression(node.getExpression());
			return true;
		}
		public boolean visit(ExpressionStatement exs) {
			inspectExpression(exs.getExpression());
			return false;
		}

		/**
		 * Count occurrences of && and || (conditional and or)
		 * Fix for BUG 740253
		 * @param ex
		 */
		private void inspectExpression(Expression ex) {
			if ((ex != null)&&(source != null)) {
				int start = ex.getStartPosition();
				int end = start + ex.getLength();
				String expression = source.substring(start, end);
				char[] chars = expression.toCharArray();
				for (int i = 0; i < chars.length-1; i++) {
					char next = chars[i];
					if ((next == '&' || next == '|')&&(next == chars[i+1])) {
						cyclomatic++;
					}
				}
			}
		}
	}
}